package edu.cmu.ece.ds.droidchat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Stack;

import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.util.Log;

import com.facebook.android.Facebook;

import edu.cmu.ece.ds.droidchat.controller.ImageDownloader;
import edu.cmu.ece.ds.droidchat.message.CreateSessionInfoResponseMessage;
import edu.cmu.ece.ds.droidchat.model.ChatSessionInfo;
import edu.cmu.ece.ds.droidchat.model.LoginSessionAttribute;
import edu.cmu.ece.ds.droidchat.model.StringConstants;
import edu.cmu.ece.ds.droidchat.model.UserInfo;

/**
 * App-wide shared methods come here.
 */
public class Toolbox {
    private final static String TAG = "Toolbox";
    private static Facebook facebook = new Facebook(
            StringConstants.FACEBOOK_APP_ID);
    private static Hashtable<String, UserInfo> userInfoHashtable = new Hashtable<String, UserInfo>(
            400);

    private static Hashtable<String, ChatSessionInfo> chatSessionHashtable = new Hashtable<String, ChatSessionInfo>();

    public final static ImageDownloader imageDownloader = new ImageDownloader();
    // private final static Gson gson = new Gson();
    private static UserInfo myInfo;

    public static Drawable loadFacebookProfilePicture(String fbId) {
        try {
            InputStream is = (InputStream) new URL(
                    StringConstants.URL_PREFIX_FACEBOOK_GRAPH + fbId
                            + StringConstants.URL_POSTFIX_FACEBOOK_PICTURE)
                    .getContent();
            Drawable d = Drawable.createFromStream(is, "src name");
            is.close();
            return d;
        } catch (IOException e) {
            Log.e(TAG, "LoadFacebookProfileImage error.");
            return null;
        }
    }

    public static Facebook getFacebookObject() {
        return facebook;
    }

    /**
     * 'username' is the invitee's username
     * 
     */
    public static String addNewOneOnOneChatSessionInfoByUsername(String username,
            LoginSessionAttribute login) {
        Iterator<ChatSessionInfo> itr = chatSessionHashtable.values()
                .iterator();

        boolean chatSessionExists = false;

        ChatSessionInfo chatSession = null;
        while (itr.hasNext()) {
            chatSession = itr.next();

            if (chatSession.getUsername().equals(username)) {
                if (chatSession.participants.size() == 1) {
                    chatSessionExists = true;
                }
            }
        }

        if (chatSessionExists == false) {
        	ChatSessionInfo newInfo = null;
        	if(login==null){
        		// facebook facebook facebook
        		newInfo = new ChatSessionInfo(login);
        		newInfo.setUsername(username);
            	chatSessionHashtable.put(newInfo.getSessionid(), newInfo);
        	}else{
        		newInfo = new ChatSessionInfo(login.getUsername(), login);
            	newInfo.setUsername(username);
            	chatSessionHashtable.put(newInfo.getSessionid(), newInfo);
        	}
            return newInfo.getSessionid();
        } else {
            return chatSession.getSessionid();
        }
    }

    public static void putCreationKey(CreateSessionInfoResponseMessage msg) {
        String invitee = msg.getUsername();

        System.out.println(" in putCreationKey(), invitee is " + invitee);
        
        Iterator<ChatSessionInfo> itr = chatSessionHashtable.values()
                .iterator();

        while (itr.hasNext()) {
            ChatSessionInfo chatSession = itr.next();

            if (chatSession==null){
            	System.out.println(" chatSession is null");
            } else {
            	System.out.println(" chatSession is not null");
            }
            
            if (chatSession.getUsername()==null){
            	System.out.println(" chatSession.getUsername() is null");
            } else {
            	System.out.println(" chatSession.getUsername() is not null");
            }
            
            if (chatSession.getUsername().equals(invitee) && chatSession.isPending()) {
                chatSession.setPublicExponent(msg.getPublic_exponent());
                chatSession.setPublicModulus(msg.getPublic_modulus());
            }
        }
    }

    public static void addParticipant(String username, String chatSessionID){

        if(chatSessionHashtable.containsKey(username)){
            ChatSessionInfo info = chatSessionHashtable.get(chatSessionID);

            info.participants.add(username);

        }

    }

    public static void checkRoundNumber(int latestRound) {
        Stack<String> usernameToBeRemoved = new Stack<String>();

        for (String username : userInfoHashtable.keySet()) {
            UserInfo cur = userInfoHashtable.get(username);
            if (cur.getRound() + 3 < latestRound) {
                // too old...
                Log.d("checkRoundNumber", cur.getUsername() + " is too old");
                usernameToBeRemoved.push(username);
            }
        }

        while (!usernameToBeRemoved.isEmpty()) {
            Log.d("checkRoundNumber", usernameToBeRemoved.peek()
                    + " is removed");
            userInfoHashtable.remove(usernameToBeRemoved.pop());
        }
    }

    // returns the current number of neighbors in the hashtable
    public static int numNeighbors() {
        return userInfoHashtable.keySet().size();
    }

    public static Collection<UserInfo> retrieveAllUsers() {
        return userInfoHashtable.values();
    }

    public static UserInfo retrieveUserInfoByUsername(String username) {
        UserInfo uInfo = null;

        if (userInfoHashtable.containsKey(username)) {
            uInfo = userInfoHashtable.get(username);
        }
        return uInfo;
    }

    public static UserInfo retrieveUserInfo(String username) {
        UserInfo uInfo = null;
        if ((uInfo = userInfoHashtable.get(username)) == null) {
            // TODO: networkservice should get uInfo from server, and then put
            // it to userInfoHashtable (putToUserInfoHashtable) before it sends
            // out the broadcast
            Log.e(TAG, "User info for username=" + username
                    + " not found in hashtable.");
            uInfo = new UserInfo("Faulty Username", "FAULTING Nickname");
        }
        return uInfo;
    }

    public static ChatSessionInfo getSessionAttr(String sessionid) {
        ChatSessionInfo attr = null;

        if(chatSessionHashtable==null){
        	System.out.println("chatSessionHashtable is null");
        }
        
        if(sessionid==null){
        	System.out.println("sessionid is null");
        }
        
        if (chatSessionHashtable.containsKey(sessionid)) {
            attr = chatSessionHashtable.get(sessionid);
        }
        return attr;
    }

    public static void addNewChatSession(ChatSessionInfo attr) {
        if (chatSessionHashtable.get(attr) == null) {
            // put a new chat session attribute into the table, we have a new
            // session
            // it to userInfoHashtable (putToUserInfoHashtable) before it sends
            // out the broadcast
            Log.e(TAG, "new session with id=" + attr.getSessionid()
                    + " is added");
            chatSessionHashtable.put(attr.getSessionid(), attr);
        }
    }

    // debug only
    public static ArrayList<UserInfo> getUserInfoArray() {
        return new ArrayList<UserInfo>(userInfoHashtable.values());
    }

    public static void putToUserInfoHashtable(UserInfo userInfo) {
        Toolbox.userInfoHashtable.put(userInfo.getUsername(), userInfo);
    }

    public static UserInfo getMyInfo() {
        return myInfo;
    }

    public static void setMyInfo(UserInfo myInfo) {
        Toolbox.myInfo = myInfo;
    }

    public static String LoadFacebookStatus(String fbId) {
        // TODO: NOT COMPLETED YET.
        String statusString = "You should not see this line!";
        try {
            BufferedReader in = new BufferedReader(new InputStreamReader(
                    (InputStream) new URL(
                            StringConstants.URL_PREFIX_FACEBOOK_GRAPH_SECURED
                                    + fbId + "/statuses" + "?access_token="
                                    + facebook.getAccessToken()).getContent()));
            String line = "";
            String statusStart = "\"message\":\"";
            int statusStartLen = statusStart.length();

            while ((line = in.readLine()) != null) {
                Log.e(TAG, "while, line=" + line);
                if (line.indexOf(statusStart) != -1) {
                    break;
                }
            }

            Log.e(TAG, "line=" + line);
            if (line != null) {
                int startIndex = line.indexOf(statusStart);
                if (startIndex != -1) {
                    line = line.substring(startIndex + statusStartLen);
                    int endIndex = line.indexOf('\"');
                    statusString = line.substring(0, endIndex);
                } else {
                    statusString = "Not yet implemented.";
                }
            } else {
                statusString = "";
            }
            in.close();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return statusString;
    }

    // debug only
    public static void prepareFriendIds(String fbFriendList)
            throws UnsupportedEncodingException {
        // gson.fromJson(fbFriendList, classOfT);

        // will use json to parse.

        // separators
        String nameStart = "{\"name\":\"";
        String idStart = "\"id\":\"";
        int idStartLen = idStart.length();
        int nameStartLen = nameStart.length();
        int startIndex = 0;
        int endIndex = 0;

        UserInfo temp;
        while (startIndex != -1) {
            startIndex = fbFriendList.indexOf(nameStart);
            fbFriendList = fbFriendList.substring(startIndex + nameStartLen);
            endIndex = fbFriendList.indexOf('\"');
            String name = new String(fbFriendList.substring(0, endIndex)
                    .getBytes(), "UTF-8");
            startIndex = fbFriendList.indexOf(idStart);
            fbFriendList = fbFriendList.substring(startIndex + idStartLen);
            endIndex = fbFriendList.indexOf('\"');
            String id = fbFriendList.substring(0, endIndex);
            fbFriendList.substring(endIndex);
            temp = new UserInfo(id, name);
            userInfoHashtable.put(id, temp);
        }
    }

    public static void prepareMyInfo(String myInfoString) {
        String idStart = "{\"id\":\"";
        String nameStart = "\"name\":\"";

        int idStartLen = idStart.length();
        int nameStartLen = nameStart.length();
        int startIndex = 0;
        int endIndex = 0;

        String id, name;

        startIndex = myInfoString.indexOf(idStart);
        myInfoString = myInfoString.substring(startIndex + idStartLen);
        endIndex = myInfoString.indexOf('\"');
        id = myInfoString.substring(0, endIndex);
        startIndex = myInfoString.indexOf(nameStart);
        myInfoString = myInfoString.substring(startIndex + nameStartLen);
        endIndex = myInfoString.indexOf('\"');
        name = myInfoString.substring(0, endIndex);
        UserInfo myInfo = new UserInfo(id, name);
        Toolbox.setMyInfo(myInfo);
        Log.d(TAG, myInfo.toString());
    }

    static enum ParseState {
        NORMAL, ESCAPE, UNICODE_ESCAPE
    }

    /**
     * Converts Unicode to UTF8.
     *
     * @param s
     *            The string in unicode.
     *
     * @see http://www.xinotes.org/notes/note/813/
     */
    public static String convertUnicodeEscape(String s) {
        char[] out = new char[s.length()];

        ParseState state = ParseState.NORMAL;
        int j = 0, k = 0, unicode = 0;
        char c = ' ';
        for (int i = 0; i < s.length(); i++) {
            c = s.charAt(i);
            if (state == ParseState.ESCAPE) {
                if (c == 'u') {
                    state = ParseState.UNICODE_ESCAPE;
                    unicode = 0;
                } else { // we don't care about other escapes
                    out[j++] = '\\';
                    out[j++] = c;
                    state = ParseState.NORMAL;
                }
            } else if (state == ParseState.UNICODE_ESCAPE) {
                if ((c >= '0') && (c <= '9')) {
                    unicode = (unicode << 4) + c - '0';
                } else if ((c >= 'a') && (c <= 'f')) {
                    unicode = (unicode << 4) + 10 + c - 'a';
                } else if ((c >= 'A') && (c <= 'F')) {
                    unicode = (unicode << 4) + 10 + c - 'A';
                } else {
                    throw new IllegalArgumentException(
                            "Malformed unicode escape");
                }
                k++;

                if (k == 4) {
                    out[j++] = (char) unicode;
                    k = 0;
                    state = ParseState.NORMAL;
                }
            } else if (c == '\\') {
                state = ParseState.ESCAPE;
            } else {
                out[j++] = c;
            }
        }

        if (state == ParseState.ESCAPE) {
            out[j++] = c;
        }

        return new String(out, 0, j);
    }

    /**
     * a simple helper method translate IP in the "xxx.xxx.xxx.xxx" format into
     * a 8-byte long
     */
    public static long ipToInt(String addr) {
        String[] addrArray = addr.split("\\.");

        long num = 0;
        for (int i = 0; i < addrArray.length; i++) {
            int power = 3 - i;

            num += ((Integer.parseInt(addrArray[i]) % 256 * Math
                    .pow(256, power)));
        }
        return num;
    }

    public static String intToIp(int i) {
        return ((i >> 24) & 0xFF) + "." + ((i >> 16) & 0xFF) + "."
                + ((i >> 8) & 0xFF) + "." + (i & 0xFF);
    }

    /*
     * start the ChatWindowActivity to chat with the user corresponds to uInfo
     */
    public static void createChatWindowActivity(Context context, UserInfo uInfo) {
        Intent i = new Intent(context, ChatWindowActivity.class);
        i.putExtra(StringConstants.EXTRA_ANOTHER_USER_USER_NAME,
                uInfo.getUsername());
        i.putExtra("username", uInfo.getUsername());
        context.startActivity(i);
    }

    public static ArrayList<BigInteger> getCreationKey(String chatSessionID) {

        ArrayList<BigInteger> pubKeys = new ArrayList<BigInteger>();

        if(chatSessionHashtable == null){
        	System.out.println("chatSessionHashtable is null");
        }
        
        if(chatSessionHashtable.containsKey(chatSessionID)){
            ChatSessionInfo info = chatSessionHashtable.get(chatSessionID);
            pubKeys.add(info.getPublicExponent()); // exponent
            pubKeys.add(info.getPublicModulus()); // modulus
            return pubKeys;
        }

        return null;
    }

}
